package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	// 参数确认
	if len(os.Args) != 4 {
		fmt.Print("Need 3 args, Respectively are input-password-output\n")
		return
	}
	// 整理参数
	inputPath := os.Args[1]
	password := make([]byte, len(os.Args[2]))
	for i, c := range os.Args[2] {
		password[i] = byte(c)
	}
	outputPath := os.Args[3]

	// 打开输入文件
	input, err := os.Open(inputPath)
	if err != nil {
		fmt.Print("open input file fail\n")
		fmt.Printf("%v\n", err)
		return
	}

	// 获取输入文件信息
	stat, err := input.Stat()
	if err != nil {
		fmt.Printf("%v\n", err)
	}
	inputSize := stat.Size()
	inputSizeMB := float64(inputSize) / 1024 / 1024
	fmt.Printf("input %v MB\n", inputSizeMB)

	// 创建输出文件
	output, err := os.Create(outputPath)
	if err != nil {
		fmt.Print("open output file fail\n")
		fmt.Printf("%v\n", err)
		return
	}

	// 开始处理
	readOffset := 0
	writeOffset := 0
	// 每个线程文件缓冲区的大小
	buffSize := 1024 * 1024 * 32
	// 启用的线程数，由于存在线程等待问题，貌似设置成 4 效率就不错了
	coreNum := 4
	// 每个线程的 chan
	chanList := make([]chan *[]byte, coreNum)
	// 是否读取完了全部输入文件
	readOver := false
	// 是否写入完了全部输出文件
	writeOver := false

	for readOver == false {
		// 读取文件，送给 go 加密
		for i := 0; i < coreNum; i++ {
			buff := make([]byte, buffSize)
			readLen, err := input.Read(buff)
			// 检查是不是读完了
			if err == io.EOF || readLen == 0 {
				readOver = true
				break
			}
			buff = buff[:readLen]

			chanList[i] = make(chan *[]byte)
			go goMi(&buff, password, readOffset, chanList[i])
			readOffset += readLen
		}

		// 等待通道返回结果并写入
		for i := 0; i < coreNum; i++ {
			// 防止对空 chan 取数据
			if writeOver {
				break
			}
			buff := <-chanList[i]
			_, err := output.Write(*buff)
			if err != nil {
				fmt.Print("write output file fail\n")
				fmt.Printf("%v\n", err)
				return
			}
			writeOffset += len(*buff)

			// 输出进度用
			fmt.Print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b")
			fmt.Printf("%v %%", float64(writeOffset)/float64(inputSize)*100)

			// 全部写完了
			if writeOffset == int(inputSize) {
				writeOver = true
				fmt.Printf("\ntransform done!\n")
			}
		}
	}
	// 完事收工
	err = output.Close()
	if err != nil {
		fmt.Print("close output file fail\n")
		fmt.Printf("%v\n", err)
	}
}

// go 调用这个方法来加密 []byte
func goMi(bytes *[]byte, password []byte, offset int, c chan *[]byte) {
	mi(bytes, password, offset)
	c <- bytes
}

// 输入一个 []byte 和密码以及这段byte位于文件中的offset，会对 byte 进行原地处理，变成异或后的结果
func mi(bytes *[]byte, password []byte, offset int) {
	for i, c := range *bytes {
		(*bytes)[i] = password[(i+offset)%len(password)] ^ c
	}
}
